package me.leon.ade

import java.io.File
import java.io.FileReader
import java.io.FileWriter
import java.io.RandomAccessFile
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
import kotlin.concurrent.thread
import kotlin.math.min
import me.leon.ade.NetHelper.ILLEGAL_FILE_NAME_PATTERN

/**
 * 多线程下载 和 断点续传 Properties方法, 只有但配置文件,但无法多线程实时存储文件 采用properties， 主动停止时存取进度，减少I/O
 *
 * feature:
 * 1. 自定义文件名,及目录
 * 2. 自定义线程数
 * 3. 自定义请求头
 * 4. 自定义 bufferSize
 */
class MultiThreadDownloader2(
    val path: String =
        "http://d0.ananas.chaoxing.com/download/e9c3d6ee0914e4555886d907d4e684b4?at_=" +
            "1613979050848&ak_=aeb2d0e7ff4cca655033b541c3f8bc22&ad_=ca01c86bc3fe8" +
            "e630fb07d1966bf7e5e&fn=%E3%80%8A%E6%95%B0%E6%8D%AE%E5%8E%8B%E7%B" +
            "C%A9%E5%AF%BC%E8%AE%BA%EF%BC%88%E7%AC%AC4%E7%89%88%EF%BC%89%E3%80" +
            "%8B.%28%E8%B4%BE%E6%B4%AA%E5%B3%B0%29.%5BPDF%5D%40jb51.net",
    //    val path: String =
    // "https://shidu-temp.oss-cn-hangzhou.aliyuncs.com/video/%E5%8D%8A
    // %E9%98%B3%20-%20%E4%B8%80%E6%9B%B2%E7%9B%B8%E6%80%9D.mp3",
    val targetDir: String = "/multi/files/",
    var threadCount: Int = Runtime.getRuntime().availableProcessors(),
    val headers: MutableMap<String, Any> = mutableMapOf(),
    val bufferSize: Int = 64 * 1024,
    val minBlockSize: Long = 8 * 1024 * 1024,
    val onProgress: (Long, Long) -> Unit = { _, _ -> },
    val onCompleted: (String) -> Unit = {},
    val onError: (String) -> Unit = {}
) {
    private var completedCount = 0
    private var filePath = ""
    var md5 = ""
    var fileName = ""
    var configPath: File? = null
    var fileSize = 0L
    var properties: Properties = Properties()
    var reader: FileReader? = null
    val threads: MutableList<DownloadThread> = mutableListOf()
    var isCompleted = false

    /** 下载文件 */
    @Throws(Exception::class)
    fun download() {
        threads.clear()
        properties.clear()
        completedCount = 0
        if (!File(targetDir).exists()) File(targetDir).mkdirs()
        // 不下载文件 获取文件大小  文件名称
        URL(path).openConnection().safeAs<HttpURLConnection>()?.run {
            requestMethod = "HEAD"
            connectTimeout = 10000
            setRequestProperty("user-agent", NetHelper.COMMON_UA)
            headers.forEach { setRequestProperty(it.key, it.value.toString()) }
            if (responseCode == 200) {
                // 阿里云 文件md5
                //                if (!getHeaderField("etag").isNullOrEmpty()) {
                //                    md5 = getHeaderField("etag").removeSurrounding("\"")
                //                }
                fileName =
                    NetHelper.getNetFileName(this, path)
                        .also { println(it) }
                        .replace(ILLEGAL_FILE_NAME_PATTERN.toRegex(), "_")
                        .takeIf { fileName.isNullOrEmpty() }
                        ?: fileName
                println(fileName)

                configPath = File(targetDir, "${fileName}_${path.hashCode()}.conf")
                try {
                    if (configPath!!.exists()) {
                        reader = FileReader(configPath)
                        properties.load(reader)
                    } else {
                        // clear old file
                        println("delete old file " + File(targetDir, fileName).delete())
                    }
                } catch (e: java.lang.Exception) {
                    e.printStackTrace()
                }
                // 在本地创建一个与资源同样大小的文件来占位
                RandomAccessFile(
                        File(targetDir, fileName).also { filePath = it.absolutePath },
                        "rw"
                    )
                    .apply { setLength(contentLengthLong) }
                    .use {
                        // 将下载任务分配给每个线程
                        // println(contentLengthLong)
                        contentLengthLong
                            .also { fileSize = it }
                            .slice(threadCount, minBlockSize)
                            .also { threadCount = it.size }
                            .forEachIndexed { index, longRange ->
                                DownloadThread(index, longRange.first, longRange.last)
                                    .also { threads.add(it) }
                                    .start()
                            }
                    }
            }
        }
    }

    /** 异步下载 */
    fun downloadAsync() {
        thread { download() }
    }

    @Synchronized
    private fun saveProgress() {
        if (!isCompleted) {
            FileWriter(configPath, false).use { properties.store(it, "multi thread download") }
        } else {
            println("————————isCompleted")
            println(configPath?.delete())
        }
    }

    fun pause() {
        var isStoped = false
        for (thread in threads) {
            if (thread.isAlive) {
                isStoped = true
                //                thread.stop()
                thread.stopKindly()
            }
        }
        reader?.close()
        if (isStoped) saveProgress() else println(" already downloaded, no need to persist!")
    }

    // 下载线程
    inner class DownloadThread(val threadId: Int, var startIndex: Long, val endIndex: Long) :
        Thread() {
        val showProgressInterval = 500L
        var lastShowTime = 0L
        var stoped = false

        /**
         * Thread#stop Thread#interept may not works,or occurs errors, suggest to use a exit flag to
         * stop thread
         */
        fun stopKindly() {
            stoped = true
        }

        override fun run() {

            println("thread $threadId start downloading $startIndex  $endIndex  $fileSize")
            try {
                properties.getProperty("thread$threadId")?.let { startIndex = it.toLong() }
                startIndex = min(startIndex, endIndex)
                if (startIndex == endIndex) {
                    completedCount++
                    println("thread $threadId already downloaded")
                    return
                }
                // 分段请求网络连接,分段将文件保存到本地.
                URL(path).openConnection().safeAs<HttpURLConnection>()?.run {
                    requestMethod = "GET"
                    connectTimeout = 10000
                    setRequestProperty("user-agent", NetHelper.COMMON_UA)
                    // 设置分段下载的头信息。  Range:做分段数据请求用的。格式: Range bytes=0-1024  或者 bytes:0-1024
                    setRequestProperty("Range", "bytes=$startIndex-$endIndex")
                    headers.forEach { setRequestProperty(it.key, it.value.toString()) }
                    println("thread_$threadId from $startIndex to  $endIndex  $fileSize")
                    if (responseCode == 206) { // 200：请求全部资源成功， 206代表部分资源请求成功
                        val randomAccessFile = RandomAccessFile(File(targetDir, fileName), "rw")
                        randomAccessFile.seek(startIndex)
                        val buffer = ByteArray(bufferSize)
                        var length = -1
                        var total = 0L

                        randomAccessFile.use { raf ->
                            inputStream.use { input ->
                                while (!stoped && input.read(buffer).also { length = it } > 0) {
                                    raf.write(buffer, 0, length)
                                    total += length
                                    showProgress(total)
                                    properties.setProperty(
                                        "thread$threadId",
                                        (startIndex + total).toString()
                                    )

                                    saveProgress()
                                }
                            }
                        }
                        // eg.  Range 0-1024, startIndex = 0 , endIndex = 1024,lenght = 1025,
                        // rang starts from 0 , and lenght starts from 1,
                        // so lenght is next startIndex
                        // if reads 512 byte,request range = 0-511,next startIndex 512,request range
                        //  = 512-1024
                        if (startIndex + total == endIndex + 1) {
                            println("线程 $threadId 下载完毕")
                            completedCount++
                        } else {
                            println("${startIndex + total}  $endIndex 线程 $threadId 终止")
                        }
                        if (completedCount == threadCount) {
                            isCompleted = true
                            reader?.close()
                            println(configPath?.also { println(it.absolutePath) }?.delete())
                            onCompleted(filePath)
                        }
                    } else {
                        println("code $responseCode. 服务器不支持多线程下载")
                        onError("code $responseCode. 服务器不支持多线程下载")
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }

        private fun showProgress(total: Long) {
            if (System.currentTimeMillis() - lastShowTime < showProgressInterval) {
                return
            }
            lastShowTime = System.currentTimeMillis()
            println(
                "thread_$threadId :  ${total}/${endIndex - startIndex}  (${
                    String.format(
                        "%.2f%%",
                        100 * total.toFloat() / (endIndex - startIndex)
                    )
                })"
            )
        }
    }

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            var isComplete = false
            val multiThreadDownloader2 =
                MultiThreadDownloader2(
                        onCompleted = {
                            println(
                                "filePath: $it  md5: ${
                        it.fileMd5()
                            .also { println(it + (it == "C8773345B4783FE4AC676C646B1239A9")) }
                    } "
                            )
                            isComplete = true
                        }
                    )
                    .apply { fileName = "ttt.pdf" }
            multiThreadDownloader2.download()

            thread {
                Thread.sleep(3000)
                multiThreadDownloader2.pause()
                println("停止")
            }
            while (!isComplete) {
                // dead loop
            }
        }
    }
}
